package fm

import (
	"bytes"
	"io"
	"math"
	"math/cmplx"

	"gitee.com/thubcc/mobile/utils"
	"github.com/prometheus/common/log"
	"gopkg.in/hraban/opus.v2"
)

type DF struct {
	last complex128
}

func NewDF() *DF {
	return &DF{1.0 + 0.i}
}

func (df *DF) Done(din []complex128) (dout []complex128, err error) {
	dout = make([]complex128, len(din))
	for i, v := range din {
		dout[i] = complex(cmplx.Phase(v*cmplx.Conj(df.last)), 0.)
		df.last = v
	}
	return
}

type carrier struct {
	c complex128
}

func (c *carrier) Done(din []complex128) (dout []complex128, err error) {
	s := 0. + 0.i
	for i, v := range din {
		p := float64(i) * 19. / 192. * 2 * math.Pi
		s += complex(real(v)*math.Cos(p), -real(v)*math.Sin(p))
	}
	s /= complex(cmplx.Abs(s), 0)
	c.c += 0.1 * (s - c.c)
	dout = make([]complex128, len(din))
	p0 := cmplx.Phase(c.c)
	for i, v := range din {
		p := float64(i)*19./192.*2*math.Pi + p0
		tc := math.Cos(p)
		ts := math.Sin(p)
		dout[i] = complex(real(v)*tc*tc, real(v)*ts*ts)
	}
	return
}

type FM struct {
	chans  utils.CompositeChan
	inf    utils.RFInterface
	f      float64
	conv   *utils.Convert
	dc     *utils.DCRemover
	buf    bytes.Buffer
	active bool
	frame  int
}

func (fm *FM) SetF(f float64) {
	fm.conv.SetF(f)
	fm.f = f
}

func NewFM(f float64, frame int, inf utils.RFInterface) *FM {
	conv := utils.NewConvert(f)
	dc := utils.NewDC()
	chans := []utils.Chanable{
		conv,
		utils.NewCIC(2, 10),
		NewDF(),
		dc,
		&carrier{},
		// utils.NewDC(),
		utils.NewCIC(2, 4),
		utils.NewAGC(0.7),
	}
	inf.ResetPPM()
	inf.Reset()
	return &FM{chans: chans, inf: inf, f: f, conv: conv, dc: dc, frame: frame}
}

func (s *FM) Read(buf []byte) (int, error) {
	// log.Info("In read ", len(buf))
	return s.buf.Read(buf)
}

func (s *FM) StartWithOpus(r io.WriteCloser) {
	const bufferSize = 1000 // choose any buffer size you like. 1k is plenty.
	const sampleRate = 48000
	const channels = 2
	data := make([]byte, bufferSize)
	enc, err := opus.NewEncoder(sampleRate, channels, opus.AppVoIP)
	if err != nil {
		log.Error(err)
		return
	}
	err = enc.SetBitrate(16000)
	if err != nil {
		log.Error(err)
		return
	}
	err = enc.SetDTX(true)
	if err != nil {
		log.Error(err)
		return
	}
	s.active = true
	for s.active {
		rfd := s.inf.Raw(0, s.frame)
		s.inf.Run(s.frame)
		out, err := s.chans.Done(rfd)
		if err != nil {
			s.active = false
			return
		}
		var orig = make([]int16, 2*len(out))
		for i, v := range out {
			b := int16(real(v) * 32767 * 0.75)
			orig[2*i] = b
			b = int16(imag(v) * 32767 * 0.75)
			orig[2*i+1] = b
		}

		frameSize := len(orig) // must be interleaved if stereo
		frameSizeMs := float32(frameSize) / channels * 1000 / sampleRate
		switch frameSizeMs {
		case 2.5, 5, 10, 20, 40, 60:
			// Good.
		default:
			log.Errorf("Illegal frame size: %d bytes (%f ms)", frameSize, frameSizeMs)
			return
		}

		n, err := enc.Encode(orig, data)
		if err != nil {
			log.Error(err)
		}
		n, err = r.Write(data[:n])
		if err != nil {
			log.Error("Write buf failure ", n, err)
		}
	}
	r.Close()
}

func (s *FM) Start(r io.WriteCloser) {
	s.active = true
	for s.active {
		rfd := s.inf.Raw(0, s.frame)
		s.inf.Run(s.frame)
		out, err := s.chans.Done(rfd)
		if err != nil {
			s.active = false
			return
		}
		var orig = make([]byte, 4*len(out))
		for i, v := range out {
			b := int16(real(v) * 32767 * 0.75)
			orig[4*i] = byte(b)
			orig[4*i+1] = byte(b >> 8)
			b = int16(imag(v) * 32767 * 0.75)
			orig[4*i+2] = byte(b)
			orig[4*i+3] = byte(b >> 8)
		}
		n, err := r.Write(orig)
		if n != len(orig) || err != nil {
			log.Error("Write buf failure ", n, err)
		}
	}
	r.Close()
}

func (s *FM) Close() error {
	s.active = false
	return nil
}

type fmafc struct {
	df  float64
	ddf float64
}

func (fa *fmafc) adjust(isum float64) float64 {
	fa.df += isum
	fa.ddf += fa.df
	return fa.df + fa.ddf/100.
}

func (s *FM) dummyF(df float64) {
	s.conv.SetF(s.f)
}

func (s *FM) AFC(on bool, f func(float64)) {
	if on {
		afc := &fmafc{}
		c := 0
		// ondo := true
		fafc := func(sum float64) {
			// log.Info("sum", sum)
			// var df float64
			// if ondo {
			// 	df = afc.adjust(sum)
			// }
			// c += 1
			// if c == 100 {
			// 	c = 0
			// 	if ondo {
			// 		f(-df)
			// 		ondo = false
			// 	} else {
			// 		ondo = true
			// 	}
			// }
			var df float64
			df = afc.adjust(sum)
			c += 1
			if c == 100 {
				c = 0
				f(df * 1e2)
			}
		}
		s.dc.SetDf(fafc)
	} else {
		s.dc.SetDf(s.dummyF)
	}
}
